# generators.py
#
#
#
# PyEMOF: An EMOF support for Python
#
# Copyright (C) 2004, 2007 Raphael Marvie 
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
# http://www.fsf.org/licensing/licenses/gpl.html
#
# Author contact: raphael.marvie@lifl.fr
#
# Modified and adapted to conform with ECORE 2.0 by brenomachado@gmail.com


'''
Templates for the generation of EMOF repositories from the definition
of a EMOF compliant model.
'''

import os
import time

from sets import Set

import picorine.core as picorine

import emof.core as EMOF

import pyemof
import pyemof.tcore
import pyemof.telements
import pyemof.tloader
import pyemof.tdumper

__author__  = 'Breno Batista Machado <brenomachado@gmail.com>'
__date__    = 'Sun Mar 15 17:33:08 CET 2008'
__version__ = '0.10.0'


def generate(rep):
    for package in rep.Package:
        if package.isRoot:
            mname = './%s' % package.name
            _make_package(mname)
            Elements(package, pyemof.telements, mname).generate()
            Dumper(package, pyemof.tdumper, mname).generate()
            Loader(package, pyemof.tloader, mname).generate()
    
            
def _make_package(mname):
    os.mkdir(mname)
    open('%s/__init__.py' % mname, 'w').write('')

def _get_all_attributes(clss):
    """returns all attributes (defined and inherited) of a class"""
    res = clss.ownedAttribute[:]
    for c in clss.superClass:
        res.extend(_get_all_attributes(c))
        
    res = list(Set(res))
    return res

conflicts = ['and', 'del', 'for', 'is', 'raise', 'assert', 'elif', 
             'from', 'lambda', 'return', 'break', 'else', 'global', 
             'not', 'try', 'class', 'except', 'if', 'or', 'while', 
             'continue', 'exec', 'import', 'pass', 'yield', 'def', 
             'finally', 'in', 'print']

primitives = [EMOF.String, EMOF.Integer, EMOF.Boolean]

def noconflict(name):
    if name in conflicts:
        return '%s_' % name
    else:
        return name


class Elements(picorine.generator):
    '''Produces the object "repository" for handling EMOF metamodels.
    '''

    def __init__(self, model, tmodule, outdir):
        picorine.generator.__init__(self, model, tmodule, outdir)
        self._file_content = ''
        self._imports_str = list()

    def _generate_attribute(self, a):
        name = noconflict(a.name)
        if a.upper and a.upper == '*':
            if a.isComposite and \
                   not isinstance(a.type, EMOF.PrimitiveType):
                value = 'list_composite()'
            else:
                value = 'list()'
            self._file_content += self.get_tmpl('multiple_attribute_decl',
                            {'name': name, 'value': value})
        else:
            self._file_content += self.get_tmpl('simple_attribute_decl', {'name': name})

    def _generate_properties(self, a):
        aname = noconflict(a.name)
        
        if a.upper == '*':
            tmpl = 'multiple_attribute'
        elif a.isReadOnly:
            tmpl = 'readonly_attribute'
        else:
            tmpl = 'single_attribute'
        
        if a.opposite and isinstance(a.opposite, EMOF.Property):
            if a.opposite.upper == '*':
                op = 'add'
            else:
                op = 'set'
            
            self._file_content += self.get_tmpl('%s_opposite' % tmpl,
                            {'name': aname, 'op': op,
                             'oname': noconflict(a.opposite.name)})
        elif isinstance(a.type, EMOF.Enumeration):
            module = returnPackageName(a.type) + 'elements'
            
            strTmp = self.get_tmpl('import_template', {'module': module})
            if not strTmp in self._imports_str:
                self._imports_str.append(strTmp)
            
            enum_name = a.type.name
            
            self._file_content += self.get_tmpl('%s_enum' % tmpl,
                            {'name': aname, 'enum_name': enum_name, 'module': module + '.'}) 
        else:
            self._file_content += self.get_tmpl(tmpl, {'name': aname})

    def _generate_operation(self, o):
        name = noconflict(o.name)
        args = list()
        for p in o.ownedParameter:
            args.append(', %s' % p.name)
        self._file_content += self.get_tmpl('operation',
                        {'name': name, 'args': ''.join(args)})

    def _generate_eq(self, attrs):
        test = ' and '.join(['self.%s == other.%s' % \
                             (noconflict(a.name), noconflict(a.name)) \
                             for a in attrs])
        if not test:
            test = True
        self._file_content += self.get_tmpl('test_eq', {'test': test})

    def _compute_args(self, attrs):
        res = list()
        for a in attrs:
            if a.type and a.default:
                if a.type.name == 'Boolean':
                    value = a.default.capitalize()
                else:
                    value = a.default
            else:
                value = None
            if value == 'none' or value == 'true' or value == 'false':
                value = value.capitalize()
            if value == 'None' or value == 'True' or value == 'False':
                res.append('%s=%s' % (noconflict(a.name), value))
            else:
                res.append('%s=\'%s\'' % (noconflict(a.name), value))
        return res

    def _generate_type(self, t, prefix=tuple()):
        
        imports = list()
        
        classname = noconflict(t.name)
        
        if isinstance(t, EMOF.Enumeration):
            
            args = ', '.join(['\'' + el.name + '\'' for el in t.ownedLiteral])
            
            self._file_content += self.get_tmpl('enum_decl',
                                    {'name': classname,
                                     'enum_lit': args})
            return
        
        if isinstance(t, EMOF.PrimitiveType) or not t.superClass:
            superclass = 'object'
        else:
            superclass = ''
            for x in t.superClass:
                st = returnPackageName(x)
                
                if st != '' and st != returnPackageName(t):
                    superclass += st + 'elements.' + x.name + ', '
                    imports.append(st + 'elements')
                else:
                    superclass += x.name + ', '
            
            superclass = superclass.rstrip(', ')
            
        if isinstance(t, EMOF.Class):
            args = ', '.join(self._compute_args(_get_all_attributes(t)))
        else:
            args = ''
        
        for i in imports:
            strTmp = self.get_tmpl('import_template', {'module': i})
            if not strTmp in self._imports_str:
                self._imports_str.append(strTmp)
            
        self._file_content += self.get_tmpl('class_header',
                        {'name': classname,
                         'super': superclass,
                         'args': args})
                
        if isinstance(t, EMOF.Class):
            for a in _get_all_attributes(t):
                self._generate_attribute(a)
            self._generate_eq(_get_all_attributes(t))
            for a in t.ownedAttribute:
                self._generate_properties(a)
            for o in t.ownedOperation:
                self._generate_operation(o)

    def generate(self):
        '''Generates the "repository" for a metamodel definition.

        Generates the (Python) module containing the definition of
        classes for handling a model compliant to the metamodel used
        for the generation.
        '''
        name = self.model.name
        self.open('elements')
        
        classes = dict()
        clsses = list()
        
        for c in self.model.ownedType:
            if isinstance(c, EMOF.Class):
                classes[c.name] = (list(), c)
                if c.superClass:
                    elementPackage = returnPackageName(c)
                    for sp in c.superClass:
                        superClassPackage = returnPackageName(sp)
                        if elementPackage == superClassPackage:
                            classes[c.name][0].append(sp.name)
        
        clsses = createListClasses(classes)
        
        types = list()
        
        for t in clsses:
            self._generate_type(t, (name,))
            if not isinstance(t, EMOF.PrimitiveType) and not isinstance(t, EMOF.Enumeration) and not t.isAbstract:
                types.append(t.name)
        
        for t in self.model.ownedType:
            if not t in clsses:
                self._generate_type(t, (name,))
                if not isinstance(t, EMOF.PrimitiveType) and not isinstance(t, EMOF.Enumeration) and not t.isAbstract:
                    types.append(t.name)

        self._file_content += self.get_tmpl('file_footer')
        
        imports_list = ''
        for i in self._imports_str:
            imports_list += i + '\n'
        
        self._file_content = self.get_tmpl('file_header',
                        {'name': name, 'version': pyemof.__version__,
                         'date': time.ctime(), 'modules_imports': imports_list}) + self._file_content

        self.write_file(self._file_content)
        self.close()
        Core(self.model, pyemof.tcore, self.outdir).generate()
        
        for p in self.model.nestedPackage:
            odir = '%s/%s' % (self.outdir, p.name)
            _make_package(odir)
            Elements(p, pyemof.telements, odir).generate()
            

class Core(picorine.generator):
    '''Produces the object "repository" for handling EMOF metamodels.
    '''

    def __init__(self, model, tmodule, outdir):
        picorine.generator.__init__(self, model, tmodule, outdir)
        
    def generate(self):
        '''Generates the "repository" for a metamodel definition.

        Generates the (Python) module containing the definition of
        classes for handling a model compliant to the metamodel used
        for the generation.
        '''
        
        file_content = ''
        
        name = self.model.name
        self.open('core')
        
        sub_packages = list()

        imports_list = self.get_tmpl('import_template', {'module': 'elements'})
        
        for p in self.model.nestedPackage:
            sub_packages.append(returnPackageAncestor(p) + p.name)
            imports_list += self.get_tmpl('import_template', {'module': (returnPackageAncestor(p) + p.name + '.core')})

        
        types = list()
        for t in self.model.ownedType:
            if not isinstance(t, EMOF.PrimitiveType) and (isinstance(t, EMOF.Enumeration) or not t.isAbstract):
                types.append(t)

        file_content += self.get_tmpl('repository_class')
        
        for t in types:
            if not isinstance(t, EMOF.Enumeration):
                file_content += self.get_tmpl('repository_attr', {'name': t.name, 'value': 'list()'})
            else: 
                file_content += self.get_tmpl('repository_attr_enum', {'name': t.name, 'module': 'elements.'})
        
        for sp in sub_packages:
            file_content += self.get_tmpl('repository_attr_other_rep', {'name': sp.split('.')[len(sp.split('.')) - 1], 'module': sp + '.core.'})
        
        for t in types:
            if not isinstance(t, EMOF.Enumeration):
                file_content += self.get_tmpl('repository_prop', {'name': t.name})
            else:
                file_content += self.get_tmpl('repository_prop_enum', {'name': t.name})
        
        for sp in sub_packages:
            file_content += self.get_tmpl('repository_prop_other_rep', {'name': sp.split('.')[len(sp.split('.')) - 1]})

        file_content += self.get_tmpl('factory_class')
        
        for sp in sub_packages:
            file_content += self.get_tmpl('factory_attr_other_fact', {'name': sp.split('.')[len(sp.split('.')) - 1], 'module': sp + '.core.'})

        for sp in sub_packages:
            file_content += self.get_tmpl('factory_prop_other_fact', {'name': sp.split('.')[len(sp.split('.')) - 1]})

        for t in types:
            if not isinstance(t, EMOF.Enumeration):
                file_content += self.get_tmpl('factory_method', {'name': t.name, 'module': 'elements.'})
            
        file_content += self.get_tmpl('file_footer')
        
        file_content = self.get_tmpl('file_header',
                        {'name': name, 'version': pyemof.__version__,
                         'date': time.ctime(), 'modules_imports': imports_list}) + file_content

        self.write_file(file_content)
        self.close()


class Dumper(picorine.generator):
    '''Produces the "dumping utility" for EMOF repository.
    '''

    def __init__(self, model, tmodule, outdir):
        picorine.generator.__init__(self, model, tmodule, outdir)
        self._file_content = ''
        self._import_list = list()

    def _generate_type(self, pname, t):
        if isinstance(t, EMOF.Class):
            supers = list()
            for s in t.superClass:
                supers.append(s.name)
            if supers:
                s = ', '.join(supers)
            else:
                s = 'DumpElement'
            self._file_content += self.get_tmpl('dump_element_begin',
                            {'name': t.name, 'super': s,
                             'prefix': pname})
            for a in t.ownedAttribute:
                if a.upper == '*':
                    if a.isComposite:
                        if isinstance(a.type, EMOF.PrimitiveType) or \
                               a.type in primitives:
                           self._file_content += self.get_tmpl('dump_primitive_composite',
                                        {'iname': noconflict(a.name),
                                         'xname': a.name,
                                         'xtype': a.type.name}) 
                        else:
                            self._file_content += self.get_tmpl('dump_mult_composite',
                                        {'iname': noconflict(a.name),
                                         'xname': a.name})
                    else:
                        if isinstance(a.type, EMOF.PrimitiveType) or \
                               a.type in primitives:
                            print 'multiple no composite primitive'
                        else:                        
                            self._file_content += self.get_tmpl('dump_mult_attribute',
                                            {'iname': noconflict(a.name),
                                             'xname': a.name,
                                             'prefix': pname})
                else:
                    if a.isComposite:
                        self._file_content += self.get_tmpl('dump_single_composite',
                                        {'iname': noconflict(a.name),
                                         'xname': a.name})
                    else:
                        if isinstance(a.type, EMOF.PrimitiveType) or \
                               a.type in primitives:
                            self._file_content += self.get_tmpl('dump_attribute',
                                            {'iname': noconflict(a.name),
                                             'xname': a.name})
                        elif isinstance(a.type, EMOF.Enumeration):
                            self._file_content += self.get_tmpl('dump_attr_enum',
                                            {'iname': noconflict(a.name),
                                             'xname': a.name})
                        else:
                            self._file_content += self.get_tmpl('dump_attr_typed',
                                            {'iname': noconflict(a.name),
                                             'xname': a.name})
        elif isinstance(t, EMOF.PrimitiveType):
            self._file_content += self.get_tmpl('dump_primitive',
                            {'name': t.name,
                             'prefix': pname})
        else:
            print 'Not handled element type'

    def _generate_main(self, name, types):
        self._file_content += self.get_tmpl('dumper_class',
                        {'name': name})
        for t in types:
            if isinstance(t[0], EMOF.Class) and not t[0].isAbstract:
                module = returnPackageName(t[0])
                module = module.replace(self.model.name + '.', '', 1)
                self._file_content += self.get_tmpl('dumper_main_body',
                                {'name': t[0].name, 'prefix': name, 'module':module})
        self._file_content += self.get_tmpl('dumper_main_end')

    def _generate_dict(self, name, types):
        values = list()
        for t in types:
            if isinstance(t[0], EMOF.Class) and not t[0].isAbstract:
                values.append('    %selements.%s: %s' % (t[2], t[1], t[1]))
        self._file_content += self.get_tmpl('dump_dict', {'values': ',\n'.join(values)})

    def _generate_type_list(self, model):
        types = list()
        
        if isinstance(model, EMOF.Repository):
            for p in model.Package:
                if p.isRoot:
                    types.extend(self._generate_type_list(p)) 
        else:
            self._import_list.append(returnPackageAncestor(model) + model.name)
            
            for t in model.ownedType:
                if isinstance(t, EMOF.Class) or \
                       isinstance(t, EMOF.PrimitiveType) or isinstance(t, EMOF.Enumeration):
                    types.append((t, t.name, returnPackageName(t)))
            
            for p in model.nestedPackage:
                types.extend(self._generate_type_list(p)) 
        
        return types
    
    def generate(self):
        '''Generates the "dumper" utility.

        Generates the (Python) module containing the definition of
        classes for dumping a model from a repository generated using
        the same model definition.
        '''
        
        name = self.model.name
        
        self.open('dumper')
        
        
        types = self._generate_type_list(self.model)
        
        for tt in types:
            self._generate_type(name, tt[0])
            
        self._generate_dict(name, types)
        self._generate_main(name, types)
        self._file_content += self.get_tmpl('file_footer')
        
        imports_list = ''
        
        for i in self._import_list:
            strTmp = self.get_tmpl('import_template', {'module': i + '.elements'})
            imports_list += strTmp + '\n'
            
            
        self._file_content = self.get_tmpl('file_header',
                        {'name': name, 'version': pyemof.__version__,
                         'date': time.ctime(), 'modules_imports': imports_list}) + self._file_content
        
        self.write_file(self._file_content)
        self.close()


class Loader(picorine.generator):
    '''Produces the "dumping utility" for EMOF repository.
    '''

    def __init__(self, model, tmodule, outdir):
        picorine.generator.__init__(self, model, tmodule, outdir)
        self._file_content = ''
        self._import_list = list()
        
    def _generate_type(self, pname, tt):
        t = tt[0]
        if isinstance(t, EMOF.Class):
            supers = list()
            for s in t.superClass:
                supers.append(s.name)
            if supers:
                s = ', '.join(supers)
            else:
                s = 'LoadElement'
            
            module = tt[2]
            module = module.replace(self.model.name + '.', '', 1)
            self._file_content += self.get_tmpl('load_element_begin',
                            {'name': t.name, 'super': s, 'module': module})
            for a in t.ownedAttribute:
                if a.upper == '*':
                    if a.isComposite:
                        self._file_content += self.get_tmpl('load_mult_composite',
                                        {'iname': noconflict(a.name),
                                         'xname': a.name})
                    else:
                        if isinstance(a.type, EMOF.PrimitiveType) or \
                               a.type in primitives:
                            print 'multiple no composite primitive'
                        elif isinstance(a.type, EMOF.Enumeration):
                            print 'multiple no composite enumeration'
                        else:                        
                            self._file_content += self.get_tmpl('load_mult_attribute',
                                            {'iname': noconflict(a.name),
                                             'xname': a.name})
                else:
                    if isinstance(a.type, EMOF.PrimitiveType) or \
                           a.type in primitives:
                        self._file_content += self.get_tmpl('load_attribute',
                                        {'iname': noconflict(a.name),
                                         'xname': a.name})
                    elif isinstance(a.type, EMOF.Enumeration):
                        self._file_content += self.get_tmpl('load_attribute',
                                        {'iname': noconflict(a.name),
                                         'xname': a.name})
                    else:
                        self._file_content += self.get_tmpl('load_attr_typed',
                                        {'iname': noconflict(a.name),
                                         'xname': a.name})
        elif isinstance(t, EMOF.PrimitiveType):
            module = tt[2]
            module = module.replace(self.model.name + '.', '', 1)
            self._file_content += self.get_tmpl('load_primitive',
                            {'name': t.name,
                             'prefix': pname,
                             'module': module})
        else:
            print 'Not handled element type'
        self._file_content += self.get_tmpl('load_element_end')
            

    def _generate_main(self, name, types):
        self._file_content += self.get_tmpl('loader_class', {'name': name})
        for t in types:
            if isinstance(t[0],  EMOF.Class):
                self._file_content += self.get_tmpl('loader_main_body',
                                {'name': t[1], 'prefix': name})
        self._file_content += self.get_tmpl('loader_main_end')

    def _generate_dict(self, name, types):
        values = list()
        for t in types:
            values.append('    "%s:%s": %s%s' % (name, t[1], t[2] + 'elements.', t[1]))
        self._file_content += self.get_tmpl('load_dict', {'values': ',\n'.join(values)})

    def _generate_type_list(self, model):
        types = list()
        
        if isinstance(model, EMOF.Repository):
            for p in model.Package:
                if p.isRoot:
                    types.extend(self._generate_type_list(p)) 
        else:
            self._import_list.append(returnPackageAncestor(model) + model.name)
            
            for t in model.ownedType:
                if isinstance(t, EMOF.Class) or \
                       isinstance(t, EMOF.PrimitiveType) or isinstance(t, EMOF.Enumeration):
                    types.append((t, t.name, returnPackageName(t)))
            
            for p in model.nestedPackage:
                types.extend(self._generate_type_list(p)) 
        
        return types
    
    def generate(self):

        name = self.model.name
        self.open('loader')
        
        types = self._generate_type_list(self.model)
        
        for tt in types:
            self._generate_type(name, tt)
        
        
        self._generate_dict(name, types)
        self._generate_main(name, types)

        self._file_content += self.get_tmpl('file_footer')
        
        imports_list = ''
        
        for i in self._import_list:
            strTmp = self.get_tmpl('import_template', {'module': i + '.elements'})
            imports_list += strTmp + '\n'
            
        self._file_content = self.get_tmpl('file_header',
                                {'name': name, 'version': pyemof.__version__,
                                 'date': time.ctime(), 'modules_imports': imports_list}) + self._file_content
        
        self.write_file(self._file_content)
        self.close()


def returnPackageAncestor(obj):
    fqn = ''
    objt = obj
    while objt.nestingPackage:
        fqn = objt.nestingPackage.name + '.' + fqn
        objt = objt.nestingPackage
            
    return fqn

def returnPackageName(obj):
    fqn = ''
    if obj.package:
        objt = obj.package
        fqn = objt.name + '.' + fqn
        while objt.nestingPackage:
            fqn = objt.nestingPackage.name + '.' + fqn
            objt = objt.nestingPackage
            
    return fqn

def createListClasses(classes):
    clsses = list()
    removed = list()
    
    for k in classes.keys():
        if len(classes[k][0]) == 0:
            removed.append(k)
            at = classes.pop(k)
            if at[1] not in clsses:
                clsses.append(at[1])
    
    ret = createListClassesAux(classes, removed)
    removed = ret[1]
    clsses.extend(ret[0])
    classes = ret[2]
    
    while ret[3] > 0:
        ret = createListClassesAux(classes, removed)
        removed = ret[1]
        clsses.extend(ret[0])
        classes = ret[2]
        
    return clsses

def createListClassesAux(classes, removed): 
    clsses = list()
    
    for r in removed:
        for k in classes.keys():
            if r in classes[k][0]:
                classes[k][0].pop(classes[k][0].index(r)) 
    
    flg = False
    for k in classes.keys():
        if len(classes[k][0]) == 0:
            if k not in removed:
                removed.append(k)
            at = classes.pop(k)
            if at[1] not in clsses:
                clsses.append(at[1])
        else:
            flg = True
    
    return (clsses, removed, classes, flg)   

# eof
